/***************************************************************************
 *   Copyright (C) 2012                                                    *
 *   Anatole Duprat <anatole.duprat@gmail.com>                             *
 *   Charles Bulckaen  <xtrium@frequency.fr>                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation; either version 2        *
 *   of the License, or (at your option) any later version.                *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA          *
 ***************************************************************************/



#include "qfmodex.hh"

QFmodEx* QFmodEx::_instance = NULL;

QFmodEx::QFmodEx(QObject* parent) : QObject(parent)
{
    _state = Idle;

    FMOD_System_Create(&_system);
    //FMOD_System_SetOutput(_system, FMOD_OUTPUTTYPE_ALSA);
    FMOD_System_Init(_system, 1, FMOD_INIT_NORMAL, NULL);
    FMOD_System_GetMasterChannelGroup(_system, &_masterChannel);

    _sound = NULL;

    _timer = new QTimer();
    _timer->setInterval(40);
    connect(_timer, SIGNAL(timeout()), this, SLOT(timerCallback()));
}

QFmodEx::~QFmodEx()
{
    if(_sound) FMOD_Sound_Release(_sound);
    FMOD_System_Close(_system);
    FMOD_System_Release(_system);
}

QFmodEx* QFmodEx::instance()
{
    if(!_instance) _instance = new QFmodEx();
    return _instance;
}

QFmodEx::State QFmodEx::state()
{
    return _state;
}

void QFmodEx::loadMusicFile(QString filename)
{
    setState(Loading);

    if(_sound) FMOD_Sound_Release(_sound);
    FMOD_RESULT r = FMOD_System_CreateSound(_system, filename.toStdString().c_str(), FMOD_SOFTWARE | FMOD_CREATESAMPLE | FMOD_UNIQUE | FMOD_LOOP_OFF | FMOD_2D | FMOD_ACCURATETIME, 0, &_sound);
    if(r != FMOD_OK)
    {
        setState(Error);
        return;
    }

    setState(Stopped);
}

void QFmodEx::play()
{
    if(_state == Error) return;

    if(_state == Paused)
    {
       FMOD_ChannelGroup_SetPaused(_masterChannel, false);
    }
    else
    {
        FMOD_System_PlaySound(_system, FMOD_CHANNEL_FREE, _sound, false, &_playingChannel);
    }

    _timer->start();

    setState(Playing);
}

void QFmodEx::pause()
{
    if(_state == Error) return;
    FMOD_ChannelGroup_SetPaused(_masterChannel, true);
    _timer->stop();

    setState(Paused);
}

void QFmodEx::togglePlay()
{
    if(_state == Error) return;

    if(_state == Stopped)
    {
        play();
        return;
    }

    FMOD_BOOL isPaused;
    FMOD_ChannelGroup_GetPaused(_masterChannel, &isPaused);
    if(isPaused) play();
    else pause();
}

void QFmodEx::stop()
{
    FMOD_ChannelGroup_SetPaused(_masterChannel, true);
    FMOD_Channel_SetPosition(_playingChannel, 0, FMOD_TIMEUNIT_MS);
    _timer->stop();
}

void QFmodEx::seek(unsigned int time)
{
    unsigned int len;
    FMOD_Sound_GetLength(_sound, &len, FMOD_TIMEUNIT_MS);
    if(time > len) time = len;

    FMOD_Channel_SetPosition(_playingChannel, time, FMOD_TIMEUNIT_MS);
}

void QFmodEx::seekRelative(unsigned int time, SeekDirection dir)
{
    unsigned int newTime;
    FMOD_Channel_GetPosition(_playingChannel, &newTime, FMOD_TIMEUNIT_MS);

    if(dir == Backward) newTime -= time;
      else newTime += time;

    unsigned int len;
    FMOD_Sound_GetLength(_sound, &len, FMOD_TIMEUNIT_MS);
    if(newTime > len) newTime = len;

    FMOD_Channel_SetPosition(_playingChannel, newTime, FMOD_TIMEUNIT_MS);
}

void QFmodEx::setState(State newState)
{
    emit stateChanged(_state, newState);
    _state = newState;
}

void QFmodEx::timerCallback()
{
    unsigned int time;
    FMOD_Channel_GetPosition(_playingChannel, &time, FMOD_TIMEUNIT_MS);
    emit tick(time);
}

QImage* QFmodEx::audiogram(float lTime, float rTime, int width, int height)
{
    QImage* img = new QImage(QSize(width, height), QImage::Format_ARGB32);
    img->fill(0xFF303030);

    unsigned int sLength;
    FMOD_Sound_GetLength(_sound, &sLength, FMOD_TIMEUNIT_MS);
    float fLength = float(sLength) / 1000.0f;
    if(rTime > fLength) rTime = fLength;

    unsigned int lBytes = int(lTime * 44100.0f) * 4;
    unsigned int wBytes = int((rTime - lTime) * 44100.0f) * 4;

    short* ssampleBuffer; unsigned int len; short* dummy1; unsigned int dummy2;
    FMOD_Sound_Lock(_sound, lBytes, wBytes, ((void**)&ssampleBuffer), ((void**)&dummy1), &len, &dummy2);

    unsigned int readFrames = len / 4;

    unsigned int valueHop = (readFrames / width) + ((readFrames % width) % 2);

    unsigned int fi = 0;
    for(unsigned int i = 0; i < readFrames * 2; i+=valueHop*2)
    {
        if(fi >= width) break;
        float vv = 0.0f;
        for(unsigned int k = 0; k < valueHop*2; k += 2)
        {
            float l, r;
            if((i+(k/2)+1) >= len)
            {
                l = r = 0.0f;
            } else {
                l = float(ssampleBuffer[i+(k/2)]);
                r = float(ssampleBuffer[i+(k/2)+1]);
            }

            vv += (fabs(l)+fabs(r)) / 65536.0f;
        }

        vv /= float(valueHop)/2.0f;

        for(unsigned int j = 0; j < height; j++)
        {
            float cv = fabs((float(j)*2.0f)/float(height-1)-1.0f);
            if(vv >= cv) img->setPixel(fi, j, 0xFF3c3c3c);
        }

        fi++;
    }

    FMOD_Sound_Unlock(_sound, (void*)ssampleBuffer, (void*)dummy1, len, dummy2);
    return img;
}
